package com.free.mini.gateway.filter;

import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.apache.skywalking.apm.toolkit.trace.TraceContext;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;

import com.alibaba.fastjson.JSON;
import com.free.mini.common.base.ResultCode;
import com.free.mini.common.base.ResultData;
import com.free.mini.common.utils.BizTraceUtil;
import com.free.mini.gateway.constants.GatewayConstant;
import com.free.mini.gateway.utils.IpUtil;
import com.free.mini.gateway.utils.PathWildcardMatchUtil;

import cn.hutool.core.collection.CollectionUtil;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;

/**
 * 链路
 * ip黑名单
 * 防止重复提交
 * 防止过期提交
 * @author dongsufeng
 */
@Slf4j
@Component
@Order(-2)
public class BaseCheckRequestFilter implements GlobalFilter {

    /**
     * 当前应用名称
     */
    @Value("${spring.application.name:gateway}")
    private String applicationName;
    /**
     * 是否需要校验重复提交
     */
    @Value("${request.timestamp.check:false}")
    private Boolean timestampCheck;
    /**
     * 防止过期提交有效期，60秒
     */
    @Value("${request.timestamp.check.interval:60000}")
    private long timestampCheckInterval;
    @Value("${request.timestamp.left.check.interval:0}")
    private long timestampLeftCheckInterval;
    /**
     * 直接跳过filter的URL
     */
    @Value("${skip.url.list:}")
    private String skipUrlList;
    /**
     * ip黑名单
     */
    @Value("${ip.blacklist.list:}")
    private List<String> ipBlackList;
    /**
     * 是否重写trace
     */
    @Value("${trace.sky.override:false}")
    private boolean traceSkyOverride;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        try {
            GatewayContext gatewayContext = new GatewayContext(exchange);
            HttpMethod method = gatewayContext.getMethod();
            URI uri = gatewayContext.getURI();
            log.info("http method:{}, Uri:{}", method, uri);
            //open bizTraceId
            openBizTrace(gatewayContext);
            //skipUrlList
            boolean skipUrl = PathWildcardMatchUtil.wildcardMatch(uri.getPath(), skipUrlList);
            if (skipUrl) {
                if (log.isDebugEnabled()) {
                    log.debug("path:{} skip gateway request filter.",uri.getPath());
                }
                return chain.filter(exchange);
            }
            //IP黑名单
            String ip = IpUtil.getRemoteAddr(exchange.getRequest());
            if (StringUtils.isNotBlank(ip)
                    && CollectionUtil.isNotEmpty(ipBlackList) && ipBlackList.contains(ip)) {
                log.warn("request path ip blacklist check , ipBlacklist=[{}], ip=[{}]", ipBlackList, ip);
                gatewayContext.setResult(new ResultData(ResultCode.IP_BLACKLIST));
                return response(gatewayContext);
            }
            //防止重复提交
            if (StringUtils.isNotBlank(gatewayContext.getNonceId())) {
                boolean unique = false;
                //可以加缓存判断2秒内重复
                if (unique) {
                    log.warn("request nonce check fail, cacheValue=[{}], nonce=[{}]", unique, gatewayContext.getNonceId());
                    gatewayContext.setResult(new ResultData(ResultCode.RESUBMIT));
                    return response(gatewayContext);
                }
            }
            //防止过期提交
            if (timestampCheck && gatewayContext.getTimestamp() > 0){
                long currentTimestamp = System.currentTimeMillis();
                long realInterval = currentTimestamp - gatewayContext.getTimestamp();
                if (realInterval < timestampLeftCheckInterval || realInterval > timestampCheckInterval) {
                    log.warn("request timestamp check fail, currentTimestamp=[{}], timestamp=[{}], timestampCheckInterval=[{},{}], realInterval=[{}]", currentTimestamp, gatewayContext.getTimestamp(), timestampLeftCheckInterval, timestampCheckInterval, realInterval);
                    gatewayContext.setResult(new ResultData(ResultCode.REQUEST_TIMESTAMP_CHECK));
                    return response(gatewayContext);
                }
            }
            return chain.filter(exchange);
        } catch (Exception e) {
            log.error("执行异常", e);
            throw e;
        }
    }
    /**
     * 打开业务日志链路追踪
     */
    private void openBizTrace(GatewayContext gatewayContext) {
        if (log.isDebugEnabled()) {
            log.info("start open bizTrace, bizTraceId={} ...", gatewayContext.getBizTraceId());
        }
        String traceId = gatewayContext.getBizTraceId();
        if (StringUtils.isNotBlank(traceId)) {
            String skTrace = TraceContext.traceId();
            if (traceSkyOverride && StringUtils.isNotBlank(skTrace) && !traceId.endsWith(skTrace)) {
                log.info("gateway request thread rename, from: {} to: {}", Thread.currentThread().getName(), traceId);
                traceId = applicationName + "_" + skTrace;
            }
            BizTraceUtil.openTrace(applicationName, traceId);
        } else {
            BizTraceUtil.openDefaultTrace(applicationName);
        }
        gatewayContext.setBizTraceId(BizTraceUtil.currentTraceId());
        if (log.isDebugEnabled()) {
            log.debug("end open bizTrace, bizTraceId={}", gatewayContext.getBizTraceId());
        }
    }

    /**
     * 返回结果
     */
    private Mono<Void> response(GatewayContext gatewayContext) {
        ServerHttpResponse response = gatewayContext.getServerHttpResponse();
        DataBuffer buffer = response.bufferFactory()
                .wrap(JSON.toJSONString(gatewayContext.getResult()).getBytes(StandardCharsets.UTF_8));
        response.setStatusCode(HttpStatus.OK);
        // 指定编码，否则在浏览器中会中文乱码
        response.getHeaders().add(GatewayConstant.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        return response.writeWith(Mono.just(buffer));
    }
}